﻿using Artech.Core.Configuration;
using Microsoft.Practices.EnterpriseLibrary.Common.Configuration;
using Microsoft.Practices.Unity;
using Microsoft.Practices.Unity.Configuration;
using Microsoft.Practices.Unity.Utility;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Artech.Core
{
    /// <summary>
    /// The unity container based service locator.
    /// </summary>
    [ConfigurationElementType(typeof(UnityServiceLocatorData))]
    public class UnityServiceLocator: IServiceLocator
    {
        /// <summary>
        /// Gets the unity container.
        /// </summary>
        /// <value>
        /// The unity container.
        /// </value>
        public IUnityContainer UnityContainer { get; private set; }

        /// <summary>
        /// Initializes a new instance of the <see cref="UnityServiceLocator"/> class.
        /// </summary>
        /// <param name="configurationSource">The configuration source.</param>
        public UnityServiceLocator(IConfigurationSource configurationSource)
            : this(configurationSource, "")
        { }

        /// <summary>
        /// Initializes a new instance of the <see cref="UnityServiceLocator"/> class.
        /// </summary>
        /// <param name="configurationSource">The configuration source.</param>
        /// <param name="unityContainerName">Name of the unity container.</param>
        public UnityServiceLocator(IConfigurationSource configurationSource, string unityContainerName)
        {
            this.UnityContainer = UnityContainerBuilder.Current.CreateDefaultUnityContainer(configurationSource, unityContainerName);
        }

        /// <summary>
        /// Gets the service instance based on the given type and name.
        /// </summary>
        /// <param name="serviceType">The service registration type.</param>
        /// <param name="key">The registration key.</param>
        /// <returns>
        /// The service instance.
        /// </returns>
        public object GetService(Type serviceType, string key = "")
        {
            Guard.ArgumentNotNull(serviceType, "serviceType");
            if (string.IsNullOrEmpty(key))
            {
                return this.UnityContainer.Resolve(serviceType);
            }
            return this.UnityContainer.Resolve(serviceType, key);
        }

        /// <summary>
        /// Gets the service instance based on name.
        /// </summary>
        /// <typeparam name="T">The service registration type.</typeparam>
        /// <param name="key">The registration key.</param>
        /// <returns>
        /// The service instance.
        /// </returns>
        public T GetService<T>(string key = "")
        {
            object service = this.GetService(typeof(T), key);
            if (null == service)
            {
                return default(T);
            }
            return (T)service;
        }

        /// <summary>
        /// Gets all services instances based on type and name.
        /// </summary>
        /// <param name="serviceType">The service registration type.</param>
        /// <param name="keys"></param>
        /// <returns>
        /// The service instance list.
        /// </returns>
        public IEnumerable GetAllServices(Type serviceType, params string[] keys)
        {
            Guard.ArgumentNotNull(serviceType, "serviceType");
            if (null == keys || keys.Length == 0)
            {
                return this.UnityContainer.ResolveAll(serviceType);
            }
            return GetAllServiceInstances(serviceType, keys);
        }

        /// <summary>
        /// Gets all services instances based on and name.
        /// </summary>
        /// <typeparam name="T">The service registration type.</typeparam>
        /// <param name="keys">The registration key.</param>
        /// <returns>
        /// The service instance list.
        /// </returns>
        public IEnumerable<T> GetAllServices<T>(params string[] keys)
        {
            IEnumerable services = this.GetAllServices(typeof(T), keys);
            foreach (var service in services)
            {
                yield return (T)service;
            }
        }

        /// <summary>
        /// Releases the service instance.
        /// </summary>
        /// <param name="service">The service instance.</param>
        public void ReleaseService(object service)
        {
            Guard.ArgumentNotNull(service, "service");
            IDisposable disposable = service as IDisposable;
            if (null != disposable)
            {
                disposable.Dispose();
            }
        }

        /// <summary>
        /// Gets all service instances.
        /// </summary>
        /// <param name="serviceType">Type of the service.</param>
        /// <param name="keys">The keys.</param>
        /// <returns></returns>
        private IEnumerable GetAllServiceInstances(Type serviceType, string[] keys)
        {
            foreach (string key in keys)
            {
                yield return this.UnityContainer.Resolve(serviceType, key);
            }
        }


        /// <summary>
        /// Determines whether this instance [can locate service] the specified name.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="name">The name.</param>
        /// <returns>
        ///   <c>true</c> if this instance [can locate service] the specified name; otherwise, <c>false</c>.
        /// </returns>
        public bool CanLocateService<T>(string name = "")
        {
            if (string.IsNullOrEmpty(name))
            {
                return this.UnityContainer.IsRegistered<T>();
            }
            return this.UnityContainer.IsRegistered<T>(name);
        }
    }
}
